<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1034110
-->
<head>
  <title>Test for Bug 1034110</title>
  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="application/javascript"  src="/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034110">Mozilla Bug 1034110</a>
<style type="text/css">
  #pseudo.before::before { content: "before"; }
  #pseudo.after::after { content: "after"; }
</style>
<div id="pseudo"></div>
<video id="video"></video>
<p id="display"></p>
<div id="content" style="display: none">

</div>

<pre id="test">
<script type="application/javascript;version=1.7">

/** Test for Bug 1034110 **/

SimpleTest.waitForExplicitFinish();

function getWalker(node) {
  return SpecialPowers.createDOMWalker(node, true);
}

function getFirstChild(parent) {
  return SpecialPowers.unwrap(getWalker(parent).firstChild);
}

function getLastChild(parent) {
  return SpecialPowers.unwrap(getWalker(parent).lastChild);
}

function assertSamePseudoElement(which, node1, node2) {
  is(SpecialPowers.wrap(node1).nodeName, "_moz_generated_content_" + which,
     "Correct pseudo element type");
  is(node1, node2,
     "Referencing the same ::after element");
}

window.onload = function () {
  testOneAdded();
};

function testOneAdded() {
  let parent = document.getElementById("pseudo");
  var m = new MutationObserver(function(records, observer) {
    is(records.length, 1, "Correct number of records");
    is(records[0].type, "nativeAnonymousChildList", "Correct record type");
    is(records[0].target, parent, "Correct target");

    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");

    observer.disconnect();
    testAddedAndRemoved();
  });

  SpecialPowers.observeMutationEvents(m, parent, true);
  parent.className = "before";
}

function testAddedAndRemoved() {
  let parent = document.getElementById("pseudo");
  let originalBeforeElement = getFirstChild(parent);
  var m = new MutationObserver(function(records, observer) {
    is(records.length, 2, "Correct number of records");
    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
    is(records[0].target, parent, "Correct target (1)");
    is(records[1].target, parent, "Correct target (2)");

    // Two records are sent - one for removed and one for added.
    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
    assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);

    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");

    observer.disconnect();
    testRemoved();
  });

  SpecialPowers.observeMutationEvents(m, parent, true);
  parent.className = "after";
}

function testRemoved() {
  let parent = document.getElementById("pseudo");
  let originalAfterElement = getLastChild(parent);
  var m = new MutationObserver(function(records, observer) {
    is(records.length, 1, "Correct number of records");
    is(records[0].type, "nativeAnonymousChildList", "Correct record type");
    is(records[0].target, parent, "Correct target");

    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
    assertSamePseudoElement("after", records[0].removedNodes[0], originalAfterElement);

    observer.disconnect();
    testMultipleAdded();
  });

  SpecialPowers.observeMutationEvents(m, parent, true);
  parent.className = "";
}

function testMultipleAdded() {
  let parent = document.getElementById("pseudo");
  var m = new MutationObserver(function(records, observer) {
    is(records.length, 2, "Correct number of records");
    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
    is(records[0].target, parent, "Correct target (1)");
    is(records[1].target, parent, "Correct target (2)");

    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");

    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");

    observer.disconnect();
    testRemovedDueToDisplay();
  });

  SpecialPowers.observeMutationEvents(m, parent, true);
  parent.className = "before after";
}

function testRemovedDueToDisplay() {
  let parent = document.getElementById("pseudo");
  let originalBeforeElement = getFirstChild(parent);
  let originalAfterElement = getLastChild(parent);
  var m = new MutationObserver(function(records, observer) {
    is(records.length, 2, "Correct number of records");
    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
    is(records[0].target, parent, "Correct target (1)");
    is(records[1].target, parent, "Correct target (2)");

    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
    assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);

    is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
    is(records[1].removedNodes.length, 1, "Should have got removedNodes");
    assertSamePseudoElement("after", records[1].removedNodes[0], originalAfterElement);

    observer.disconnect();
    testAddedDueToDisplay();
  });

  SpecialPowers.observeMutationEvents(m, parent, true);
  parent.style.display = "none";
}

function testAddedDueToDisplay() {
  let parent = document.getElementById("pseudo");
  var m = new MutationObserver(function(records, observer) {
    is(records.length, 2, "Correct number of records");
    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
    is(records[0].target, parent, "Correct target (1)");
    is(records[1].target, parent, "Correct target (2)");

    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");

    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");

    observer.disconnect();
    testDifferentTargetNoSubtree();
  });

  SpecialPowers.observeMutationEvents(m, parent, true);
  parent.style.display = "block";
}

function testDifferentTargetNoSubtree() {
  let parent = document.getElementById("pseudo");
  var m = new MutationObserver(function(records, observer) {
    ok(false,
       "No mutation should fire when observing on a parent without subtree option.");
  });
  SpecialPowers.observeMutationEvents(m, document, true);
  parent.style.display = "none";

  // Wait for the actual mutation to come through, making sure that
  // the original observer never fires.
  var m2 = new MutationObserver(function(records, observer) {
    ok(!getFirstChild(parent), "Pseudo element has been removed, but no mutation");
    ok(!getLastChild(parent), "Pseudo element has been removed, but no mutation");
    observer.disconnect();
    testSubtree();
  });
  SpecialPowers.observeMutationEvents(m2, parent, true);
}

function testSubtree() {
  let parent = document.getElementById("pseudo");
  var m = new MutationObserver(function(records, observer) {
    is(records.length, 2, "Correct number of records");
    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
    is(records[0].target, parent, "Correct target (1)");
    is(records[1].target, parent, "Correct target (2)");

    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");

    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");

    observer.disconnect();
    testDictionaryWithoutChromePriv();
  });
  SpecialPowers.observeMutationEvents(m, document, true, true);
  parent.style.display = "block";
}

function testDictionaryWithoutChromePriv()
{
  var m = new MutationObserver(function() {});
  try {
    m.observe(document, { childList: true, get nativeAnonymousChildList() { throw "Foo1"; } } );
    ok(true, "Shouldn't throw!");
  } catch(ex) {
    ok(false, "Did throw " + ex);
  }

  try {
    m.observe(document, { childList: true, get animations() { throw "Foo2"; } } );
    ok(true, "Shouldn't throw!");
  } catch(ex) {
    ok(false, "Did throw " + ex);
  }
  
  SimpleTest.finish();
}

</script>
</pre>
</body>
</html>
